Vyvíjíme pro Android - Služby
5.1.2012
Při mých prvních pokusech s Androidem mi velmi pomohly články zejména dvou autorů. Jsou to Honza Suchan a jeho Bewátorův bloček a Tomáš Kypta se svým seriálem Vyvíjíme pro Android.
A protože kdo bere, měl by i dávat a také vhledem k tomu, že druhý jmenovaný se již více než půl roku odmlčel, zkusil bych na něj volně navázat článkem o službách (Service), které jsou po aktvitách (Activity) druhou nejvýznamější entitou v androidích aplikacích. Tím si nechci jeho seriál nijak přivlastnit a pokud by v něm chtěl pokračovat, rád název svého článku vhodně pozměním.
Ještě než začnu, upozorňuji, že moje poznatky jsou poměrně syrové, a v některých závěrech se mohu mýlit. Nebijte mě, napište mi a já se polepším. Slibuju! :-)
Deklarace v manifestu
Než začnete s vlastní implementací, je třeba službu deklarovat v "AndroidManifest.xml".
<service android:name=".maSluzba">
</service>
Tohle je nejjednodušší možná deklarace služby. Xml tag <service> je na stejné úrovni, jako tag <activity>. Samozřejmě má tento tag další možné atributy, ale jenom android:name je povinný.
Typy služeb
Dále si musíme rozmyslet, jakého typu chceme, aby naše služba byla. V zásadě máme na výběr ze dvou možností:
- Startovaná (Started) služba
- Svázaná (Bound) služba
Startovaná služba (myslím čistá startovaná služba) je spuštěna z hlavní aktivity příkazem startService(). Pak běží už nezávisle na zbytku aplikace, plní úkol, který je jí zadán startovacím intentem a po jeho splnění se sama ukončí příkazem stopSelf(). Samozřejmě je možné ji ukončit i zvenku příkazem stopService() (nečekaně:-).
Svázaná služba se spouští příkazem bindService(). Taková služba je pak svázaná s aktivitou, která ji spustila a skončí po "odvázání" příkazem unbindService(). K jedné službě se může postupně "přivázat" více aktivit. Pak služba skončí po "odvázázání" poslední z nich.
Další možností je kombinace obého. To je situace, kdy službu nejprve "nastartujete" a pak si ji ještě "přivážete". Vznikne služba, ke které se lze zvenku dle libosti připojovat a odpojovat a ona stále běží a běží a běží.... :-) Ukončí se opět sama, nebo ji ukončíte zvenku.
Tady bych se trochu pozastavil, protože jsem stávil spoustu času výzkumem, jak dosáhnout určitého chování, abych pak zjistil, že to tak funguje automaticky. Chtěl jsem službu, která poběží nezávisle na aktivitě, občas si s ní vymění nějaká data, když se aktivita zastaví tlačítkem "Zpět", služba zůstane běžet, při daším spuštění aktivity se k ní tato opět připojí a když aktivitu killnu dlouhým podržením "Zpět", služba skončí zároveň s ní. Takže není potřeba dělat vůbec nic, prostě to tak funguje :-).
Životní cyklus služby
Vlevo je životní cyklus startované služby, vpravo pak svázané služby.
public class maSluzba extends Service {
IBinder mBinder;
/* rozhraní pro komunikaci s volajícím procesem */
@Override
public void onCreate() {
/* Zavolá se když je služba poprvé spuštěna startService() nebo bindService() */
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
/* Zavolá se v případě, že služba byla byla spuštěna jako startovaná příkazem startService().
V patrametru intent je k dispozici obsah spouštěcího intentu.*/
return mStartMode;
/* Touto návratovou hodnotou se určí, jak se má služba zachovat v případě, že je ukončena systémem
START_NOT_STICKY - znovu nespouštět, leda by zde byly nevyřízené intenty.
START_STICKY - službu restartovat přes onStartCommand s nulovým intentem, nebo nevyřízeným intentem.
START_REDELIVER_INTENT - službu restartovat posledním intentem.*/
}
@Override
public IBinder onBind(Intent intent) {
/*Zavolá se v případě, že služba byla spuštěna jako svázaná, příkazem bindService().*/
return IBinder;
/*Vrací instanci rozhraní pro komunikaci se službou. Definice rozhraní viz níže.*/
}
@Override
public boolean onUnbind(Intent intent) {
/*Volá se při odpojení klienta.*/
return allowRebind;
/*Vrací boolean zda povolit obnovu připojení*/
}
@Override
public void onRebind(Intent intent) {
/*Volá se při obnovení připojení, pokud byla předtím volána metoda onUnbind().*/
}
@Override
public void onDestroy() {
/*Volá se při ukončení služby.*/
}
}
Takhle tedy vypadá základní kostra služby. Je to hodně podobné aktivitě a také to podobně funguje.
Implementace rozhraní, aneb nebojte se AIDL
Do implementace AIDL rozhraní se mi vůbec nechtělo, protože mi to připadalo komplikované a hledal jsem nějakou jinou možnost. Nakonec jsem se odhodlal a dobře jsem udělal. Je to docela snadné a funguje to následovně:
Nejprve vytvoříte ve složce src soubor s příponou .aidl a v něm navrhnete prototyp rozhraní. Třeba takhle:
interface InterfaceMeSluzby{
void posliPrikaz(String prikaz);
String prectiOdpoved();
}
Jakmile soubor uložíte, automaticky se ve složce gen vytvoří stejnojmenný soubor s příponou .java. Můžete se do něj podívat. Také jsem to udělal a rozhodl jsem se, že nemusím všechno pochopit a něco mohu i přijmout :-).
Zároveň se objeví chyba v implementaci služby a bude se po vás chtít implementace funkcí, jejichž prototypy jste si připravili v souboru .aidl. Upravená metoda onBind() pak bude vypadat nějak takhle:
public IBinder onBind(Intent intent) {
return new InterfaceMeSluzby.Stub(){
public void posliPrikaz(String prikaz)throws RemoteException {
Toast.makeText(getApplicationContext(),"Služba přijala příkaz "+prikaz, Toast.LENGTH_LONG).show();
}
public string prectiOdpoved() throws RemoteException {
return "odpoved od sluzby";
}
};
}
Na straně volajícího, tedy vaší aktivity pak musíte provést následující:
1) Nadeklarovat globální instanci typu třída rozhraní.
public class mojeAktivita extends Activity {
.
.
.
InterfaceMeSluzby interfaceMeSluzby;
.
.
.
}
2) Vytvořit třídu, která implementuje ServiceConnection
public class mojeAktivita extends Activity {
.
.
.
InterfaceMeSluzby interfaceMeSluzby;
.
.
.
class propojeniDoMeSluzby implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service){
interfaceMeSluzby = InterfaceMeSluzby.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {}
}
.
.
.
}
A to je tak nějak všechno. Teď už můžete službu spustit a začít s ní komunikovat. V mém příkladu se bude jednat o startovanou službu (zůstává běžet nezávisle na aktivitě), kterou si ale zároveň přivážu, abych s ní mohl komunikovat.
public class mojeAktivita extends Activity {
.
.
.
InterfaceMeSluzby interfaceMeSluzby;
.
.
.
class PropojeniDoMeSluzby implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service){
interfaceMeSluzby = InterfaceMeSluzby.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {}
}
.
.
.
@Override
protected void onResume(){
super.onResume();
try{
Intent intent = new Intent(this, mojeSluzba.class);
/*Vytvoření startovacího intentu.*/
startService(intent);
/*Nastartování služby. Neprovádí se, pokud chcete čistě svázanou službu.*/
propojeniDoMeSluzby = new PropojeniDoMeSluzby();
/*Inicializace komunikačního rozhraní.*/
bindService(intent, propojeniDoMeSluzby, Context.BIND_AUTO_CREATE );
/*Přivázání služby. Neprovádí se, pokud chcete čistě startovanou službu.*/
}
catch (Exception e){System.out.println("Vyjimka pri startovani sluzby> "+e.toString());}
/*Pokud se něco nepovede, vypíše se vám vyjímka do LogCat.*/
}
.
.
.
}
Vlastní komunikace se službou pak vypadá následovně:
.
.
.
try{
propojeniDoMeSluzby.posliPrikaz("Příkaz pro službu");
Toast.makeText(getApplicationContext(),"Služba odpověděla "+propojeniDoMeSluzby.prectiOdpoved(), Toast.LENGTH_LONG).show();
}
catch (Exception e){System.out.println("Vyjimka pri komunikaci se sluzbou> "+e.toString());}
/*Pokud se něco nepovede, vypíše se vám vyjímka do LogCat.*/
.
.
.
A to je celé. Samozřejmě chybí implementace toho, co má vlastně služba dělat. Ale to už je na vás :-).
Závěr
Teď už by pro vás neměl být problém napsat si vlastní službu, spustit ji, zakomunikovat s ní a případně ji ukončit. Pokud něco není jasné, nebojte se zeptat. Když budu vědět, rád odpovím.
Pro řešení problémů jsem jako výborný zdroj informací, samozřejmě kromě oficiálního webu http://developer.android.com, používal server stackoverflow.com. Na něm si zejména cením hodnocení kolika lidem byla odpověď prospěšná. Je to velmi dobré vodítko.
Tak to je pro dnešek vše a možná někdy nashledanou :-).